﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using TomShane.Neoforce.Controls;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace Engine2D
{
    class Interactor
    {
        private Scene mScene;
        private IList<SceneObject> mSelected;

        private Trackball mTrackball;
        private Vector3 mObjectOffset;
        private SceneObject mAnchorObject;

        private bool mMouseDown;
        private bool mMultiSelect;

        private BasicEffect mEffect;


        private enum Mode
        {
            Moving,
            Rotating
        }

        private Mode mMode;

        public Interactor(Scene scene)
        {
            mScene = scene;
            mSelected = new List<SceneObject>();

            mTrackball = new Trackball(mScene.Content);
            mTrackball.Camera = mScene.Camera;
            mObjectOffset = Vector3.Zero;
            mAnchorObject = null;

            mMouseDown = false;
            mMultiSelect = false;

            mEffect = new BasicEffect(mScene.GraphicsDevice);
        }

        public void Update(GameTime gameTime)
        {
            KeyboardState kbState = Keyboard.GetState();
            if (kbState.IsKeyDown(Keys.LeftAlt) || kbState.IsKeyDown(Keys.RightAlt))
                mMode = Mode.Rotating;
            else
                mMode = Mode.Moving;

            mMultiSelect = kbState.IsKeyDown(Keys.LeftShift) || kbState.IsKeyDown(Keys.RightShift);
        }

        public void SelectObject(SceneObject obj)
        {
            if (!IsSelected(obj))
            {
                mSelected.Add(obj);
            }
        }

        public bool IsSelected(SceneObject obj)
        {
            return mSelected.Contains(obj);
        }

        public void DeselectObject(SceneObject obj)
        {
            mSelected.Remove(obj);
        }

        public void ClearSelection()
        {
            mSelected.Clear();
        }

        public void OnMouseDown(MouseEventArgs e)
        {
            Ray ray;
            float t;
            SceneObject pickedObj;

            switch (mMode)
            {
                case Mode.Moving:
                    ray = mScene.Camera.GetPickingRay(new Vector2(e.Position.X, e.Position.Y));
                    pickedObj = mScene.PickObject(ray, out t);
                    if (pickedObj == null || (!IsSelected(pickedObj) && !mMultiSelect))
                        ClearSelection();
                    if (pickedObj != null)
                    {
                        SelectObject(pickedObj);
                        mObjectOffset = ray.Position + ray.Direction * t;
                    }
                    break;

                case Mode.Rotating:
                    ray = mScene.Camera.GetPickingRay(new Vector2(e.Position.X, e.Position.Y));
                    pickedObj = mScene.PickObject(ray, out t);
                    mAnchorObject = null;
                    if (IsSelected(pickedObj))
                        mAnchorObject = pickedObj;
                    else if (mSelected.Count > 0)
                        mAnchorObject = mSelected[mSelected.Count - 1];

                    if (mAnchorObject != null)
                    {
                        mObjectOffset = mAnchorObject.Position;
                        mTrackball.WorldPosition = mAnchorObject.Position;
                        float size = mAnchorObject.Width > mAnchorObject.Height ? mAnchorObject.Width : mAnchorObject.Height;
                        mTrackball.WorldSize = Vector3.Transform(new Vector3(size, size, size), mAnchorObject.Scale);
                        mTrackball.StartRotate(new Vector2(e.Position.X, e.Position.Y)); // sets the initial sample point
                    }
                    break;
            }
            mMouseDown = true;
        }

        public void OnMouseUp(MouseEventArgs e)
        {
            mObjectOffset = Vector3.Zero;
            mAnchorObject = null;
            mMouseDown = false;
        }

        public void OnMouseMove(MouseEventArgs e)
        {
            if (mMouseDown)
            {
                switch (mMode)
                {
                    case Mode.Moving:
                        // create a plane to drag across
                        // the normal should be the negative camera look vector
                        Plane p = new Plane(-mScene.Camera.LookAt, -mObjectOffset.Z);

                        // picking ray
                        Ray ray = mScene.Camera.GetPickingRay(new Vector2(e.Position.X, e.Position.Y));
                        float? t = ray.Intersects(p);
                        if (t != null) // shouldnt ever be null really...
                        {
                            Vector3 newPos = ray.Position + ray.Direction * t.Value;
                            Vector3 delta = newPos - mObjectOffset;
                            mObjectOffset = newPos;

                            // move the selected objects
                            foreach (SceneObject obj in mSelected)
                            {
                                obj.Position += delta;
                            }
                        }
                        break;

                    case Mode.Rotating:
                        Matrix rotation = mTrackball.Rotate(new Vector2(e.Position.X, e.Position.Y));
                        foreach (SceneObject obj in mSelected)
                        {
                            obj.Rotation *= rotation;
                        }
                        break;
                }
            }
        }

        public void OnMouseScroll(MouseEventArgs e)
        {
            if (mMode == Mode.Moving)
            {
                float factor = e.DeltaScroll * 0.001f;
                Vector3 delta = mScene.Camera.LookAt * factor;
                foreach (SceneObject obj in mSelected)
                {
                    obj.Position += delta;
                }
            }
            else
            {
                float factor = 1 + (e.DeltaScroll / 120.0f) * 0.1f; // 120 == 1 click?
                Matrix scale = Matrix.CreateScale(factor);
                foreach (SceneObject obj in mSelected)
                {
                    obj.Scale *= scale;
                }
            }
        }

        public void Draw(GameTime gameTime)
        {
            mScene.GraphicsDevice.DepthStencilState = DepthStencilState.None;

            mEffect.View = mScene.Camera.View;
            mEffect.Projection = mScene.Camera.Projection;

            mEffect.VertexColorEnabled = true;
            mEffect.TextureEnabled = false;
            mEffect.AmbientLightColor = Color.Yellow.ToVector3();
            mEffect.LightingEnabled = true;

            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            rasterizerState.FillMode = FillMode.WireFrame;
            mScene.GraphicsDevice.RasterizerState = rasterizerState;

            foreach (var obj in mSelected)
            {
                mEffect.World = obj.Scale * obj.Rotation * Matrix.CreateTranslation(obj.Position);
                foreach (EffectPass pass in mEffect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    obj.Draw(gameTime);
                }
            }

            if (mMode == Mode.Rotating && mMouseDown)
                mTrackball.Draw(mScene.GraphicsDevice);
        }
    }
}
